package org.eclipse.iota.arithmetics;

import static org.eclipse.iota.arithmetics.IotaOperationExecutor.add;
import static org.eclipse.iota.arithmetics.IotaOperationExecutor.isOne;
import static org.eclipse.iota.arithmetics.IotaOperationExecutor.multiply;
import static org.eclipse.iota.arithmetics.IotaOperationExecutor.pow;

import org.eclipse.iota.model.Addition;
import org.eclipse.iota.model.Constant;
import org.eclipse.iota.model.Exponentiation;
import org.eclipse.iota.model.Expression;
import org.eclipse.iota.model.Multiplication;
import org.eclipse.iota.model.Negation;
import org.eclipse.iota.util.IotaCopyUtil;

/**
 * Static utility methods for extracting factors and 
 * bases out of multiplications.
 * @author Christian Krause
 * @generated NOT
 */
public class IotaFactorExtractor {
	
	/**
	 * Extract the accumulated factor of an expression. In any case, the returned 
	 * constant will be a fresh one. This method does not perform any modifications.
	 * @param expression Expression.
	 * @return The extracted factor.
	 */
	public static Constant extractFactor(Expression expression) {
		
		// Constants:
		if (expression instanceof Constant) {
			return ((Constant) expression).copy();
		}
		
		// Multiplications:
		if (expression instanceof Multiplication) {
			
			Multiplication multiplication = (Multiplication) expression;
			Constant factor = new Constant(1);
			
			for (Expression operand : multiplication.getOperands()) {
				factor.multiply(extractFactor(operand));
			}
			return factor;
			
		}
		
		// Negations:
		if (expression instanceof Negation) {
			Negation negation = (Negation) expression;
			return extractFactor(negation.getOperand()).negate();
		}
		
		// Additions:
		if (expression instanceof Addition) {
			Addition addition = (Addition) expression;
			
			if (!addition.getOperands().isEmpty()) {
				
				// Compute greatest common divisor:
				Constant gcd = extractFactor(addition.getOperands().get(0)).abs();
				for (int i=1; i<addition.getOperands().size(); i++) {
					gcd.gcd(extractFactor(addition.getOperands().get(i)).abs());
				}
				return gcd;	
				
			}
		}
		
		// Exponentiations with constant exponents:
		if (expression instanceof Exponentiation) {
			Exponentiation exp = (Exponentiation) expression;
			
			// Check if the exponent is a constant factor.
			if (isOne(extractBase(exp.getRight()))) {
				Constant base = extractFactor(exp.getLeft());
				Constant exponent = extractFactor(exp.getRight());
				if (exponent.isInteger() && !exponent.isNegative()) {
					return base.pow(exponent.getValue().intValue());
				}
			}
		}
		
		// Not recognized:
		return new Constant(1);
		
	}
	
	/**
	 * Extract the accumulated base of an expression. It will be a fresh expression. 
	 * If multiplied with the result of {@link #extractFactor(Expression)}
	 * the resulting expression will be equivalent to the original expression.
	 * This method does not modify the argument expression.
	 * @param expression Expression.
	 * @return Accumulated base.
	 */
	public static Expression extractBase(Expression expression) {
		
		// The base of constants is defined as 1.
		if (expression instanceof Constant) {
			return new Constant(1);
		}
		
		// Multiplications:
		if (expression instanceof Multiplication) {
			
			Multiplication multiplication = (Multiplication) expression;			
			Expression result = new Constant(1);
			
			for (Expression operand : multiplication.getOperands()) {
				Expression base = extractBase(operand);
				result = multiply(result, base);
			}
			
			return result;
			
		}
		
		// Negations:
		if (expression instanceof Negation) {
			return extractBase(((Negation) expression).getOperand());
		}
		
		// Additions:
		if (expression instanceof Addition) {

			Addition addition = (Addition) expression;
			Constant gcd = extractFactor(addition);
			
			if (gcd.isPositive()) {
				Expression result = new Constant(0);
				for (Expression operand : addition.getOperands()) {
					Constant factor = extractFactor(operand);
					Expression base = extractBase(operand);
					result = add(result, multiply(base, factor.divide(gcd)));
				}			
				return result;
			}
			
		}
		
		// Exponentiations with constant exponents:
		if (expression instanceof Exponentiation) {
			Exponentiation exp = (Exponentiation) expression;
			
			// Check if the exponent is a constant factor.
			if (isOne(extractBase(exp.getRight()))) {
				Constant exponent = extractFactor(exp.getRight());
				if (exponent.isInteger() && !exponent.isNegative()) {
					return pow(extractBase(exp.getLeft()), exp.getRight());
				}
			}
		}
		
		// Base case:
		return IotaCopyUtil.copy(expression);
		
	}
	

}
